#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <ctype.h>
#include <time.h>

#include <iostream>
#include <fstream>

#include "zlib.h"
#include <mpi.h>
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
using namespace std;
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
#define LINGOLENGTH  4
#define LENGTHARRAY  4096
#define	ARRAYSIZE    60000
#define NUMPROCESS   4
#define MINSIZEDB    1763074800

#define UMBRAL       0.17
#define MOLSTANDARD  25000

//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//

struct LingoSim {

  char  *smile;
  int    code;
  float  lingoSimValue;

  LingoSim() {
    smile           = NULL;
    code            = 0;
    lingoSimValue   = 0;
  }

  ~LingoSim() {
    if (smile) delete[] smile;
  }

  struct LingoSim operator=(const struct LingoSim &lingoSim) {

    if (this->smile) delete[] this->smile;
    this->smile = new char[strlen(lingoSim.smile) + 1];
    strcpy(this->smile, lingoSim.smile);

    code          = lingoSim.code;
    lingoSimValue = lingoSim.lingoSimValue;

    return *this;

  };

};

#define LINGOSim struct LingoSim

//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
double obtainLingoSim(char *lingoString1, char *lingoString2) {

  int length1 = strlen(lingoString1), length2 = strlen(lingoString2);
  char tmpLingo1[length1], tmpLingo2[length2];
  int  i, j, k, z;

  //Change the Cl bu L and Br by R in the two lingo sequences
  for (i = 0, j = 0, k = 0; i < ((length1 > length2)?length1:length2); i++) {
    if (i < length1) {
      if      ((lingoString1[i] == 'l') || (lingoString1[i] == 'r')) tmpLingo1[j - 1] = (lingoString1[i] == 'l')?'L':'R';
      else if (isdigit(lingoString1[i])) tmpLingo1[j++] = '?';
      else tmpLingo1[j++] = lingoString1[i];
    }
    if (i < length2) {
      if ((lingoString2[i] == 'l') || (lingoString2[i] == 'r')) tmpLingo2[k - 1] = (lingoString2[i] == 'l')?'L':'R';
      else if (isdigit(lingoString2[i])) tmpLingo2[k++] = '?';
      else tmpLingo2[k++] = lingoString2[i];
    }
  }
  tmpLingo1[j] = '\0'; tmpLingo2[k] = '\0';

  j -= (LINGOLENGTH - 1); k -= (LINGOLENGTH - 1);
  int    L = j + k; //Total Lingos in two sequences
  double similarityValue = 0, Nai, Nbi;

  for (i = 0; i < j + k; i++) {

    char   compare[5];
    bool   analize = true;

    Nai = 0, Nbi = 0;

    if (i < j) {

      strncpy(compare, tmpLingo1 + i, 4);
      compare[4] = '\0';

      for (z = 0; z < ((j > k)?j:k); z++) {
        if (z < j && !strncmp(compare, tmpLingo1 + z, 4)) {
          if (z < i) {
            analize = false;
            break;
          }
          Nai++;
        }
        if (z < k && !strncmp(compare, tmpLingo2 + z, 4)) {
          Nbi++;
        }
      }
    }
    else {

      strncpy(compare, tmpLingo2 + i - j, 4);
      compare[4] = '\0';

      for (z = 0; z < ((j > k)?j:k); z++) {
        if (z < j && !strncmp(compare, tmpLingo1 + z, 4)) {
          analize = false;
          break;
        }
        if (z < k && !strncmp(compare, tmpLingo2 + z, 4)) {
          if (z < i - j) {
            analize = false;
            break;
          }
          Nbi++;
        }
      }
    }

    if (analize == true) similarityValue += (((Nai - Nbi) >= 0)?(1 - ((Nai - Nbi)/(Nai + Nbi))):(1 - ((Nbi - Nai)/(Nai + Nbi))));
    else                 L--;
  }

  return similarityValue/L;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void trim(char *stream) {

  int i, j;
  for (i = 0, j = 0; i < strlen(stream); i++)
    if ((stream[i] != '\n') && (stream[i] != '\r'))
        stream[j++] = stream[i];
  stream[j] = '\0';

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
char *extractElement(char *lines, int position) {

  if (position < 1) return NULL;
  char *pch = strtok (lines,", \n");
  if (position == 1) return pch;
  int intPos = 1;
  while (pch != NULL) {
    pch = strtok (NULL, ", \n");
    if (++intPos == position) return pch;
  }
  return NULL;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
long* findVirtualFlags(char *dbName, int process, long *arrFlags) {

  if (!dbName || !process) return NULL;

  long endPos;
  char buffer[LENGTHARRAY];

  gzFile completeDB = gzopen(dbName, "rb9");
  //gzseek(completeDB, MINSIZEDB, SEEK_SET);
  while (!gzeof(completeDB)) gzgets(completeDB, buffer, sizeof(buffer));
  endPos = gztell(completeDB);
  gzclose(completeDB);

  long dif = endPos/process;
  int  processTMP = 1;
  arrFlags =  new long[process];

  if (process == 1) {
    arrFlags[0] = endPos;
    return arrFlags;
  }
  else {

    completeDB = gzopen(dbName, "rb9");
    gzseek(completeDB, 0, SEEK_SET);
    while (!gzeof(completeDB)) {
      gzgets(completeDB, buffer, sizeof(buffer));
      long position = gztell(completeDB);
      if (position >= processTMP*dif) {
        arrFlags[processTMP - 1] = position;
        processTMP++;
        gzseek(completeDB, dif - abs((processTMP*dif) - position), SEEK_CUR);
      }
    }
    arrFlags[processTMP - 1] = endPos;
    gzclose(completeDB);

  }

  return arrFlags;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void MixArrayX(LINGOSim **array1, int n1, LINGOSim **array2, int n2, LINGOSim **array3) {

    int x1 = 0, x2 = 0, x3 = 0;

    while (x1 < n1 && x2 < n2)
      if (array1[x1]->lingoSimValue > array2[x2]->lingoSimValue) {

        if (array3[x3]->smile) delete array3[x3]->smile;
        array3[x3]->smile = new char[strlen(array1[x1]->smile) + 1];
        strcpy(array3[x3]->smile, array1[x1]->smile);
        array3[x3]->code          = array1[x1]->code;
        array3[x3]->lingoSimValue = array1[x1]->lingoSimValue;
        x3++; x1++;

      }
      else {

        if (array3[x3]->smile) delete array3[x3]->smile;
        array3[x3]->smile = new char[strlen(array2[x2]->smile) + 1];
        strcpy(array3[x3]->smile, array2[x2]->smile);
        array3[x3]->code          = array2[x2]->code;
        array3[x3]->lingoSimValue = array2[x2]->lingoSimValue;
        x3++; x2++;

      }
    while (x1 < n1) {

        if (array3[x3]->smile) delete array3[x3]->smile;
        array3[x3]->smile = new char[strlen(array1[x1]->smile) + 1];
        strcpy(array3[x3]->smile, array1[x1]->smile);
        array3[x3]->code          = array1[x1]->code;
        array3[x3]->lingoSimValue = array1[x1]->lingoSimValue;
        x3++; x1++;

    }
    while (x2 < n2) {

        if (array3[x3]->smile) delete array3[x3]->smile;
        array3[x3]->smile = new char[strlen(array2[x2]->smile) + 1];
        strcpy(array3[x3]->smile, array2[x2]->smile);
        array3[x3]->code          = array2[x2]->code;
        array3[x3]->lingoSimValue = array2[x2]->lingoSimValue;
        x3++; x2++;

    }

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void MixArray(LINGOSim **array, int n) {

  LINGOSim **array1, **array2;
  int n1, n2, i, j;

  if (n > 1) {

    if (n%2 == 0)   n1 = n2 = (int)n / 2;
    else         {  n1 = (int)n / 2; n2 = n1 + 1; }

    array1  = new LINGOSim*[n1];
    array2  = new LINGOSim*[n2];

    for (i = 0; i < n1; i++) {

      array1[i]  = new LINGOSim;

      array1[i]->smile = new char[strlen(array[i]->smile) + 1];
      strcpy(array1[i]->smile, array[i]->smile);
      array1[i]->code          = array[i]->code;
      array1[i]->lingoSimValue = array[i]->lingoSimValue;

    }
    for (j = 0; j < n2; i++, j++) {

      array2[j]  = new LINGOSim;

      array2[j]->smile = new char[strlen(array[i]->smile) + 1];
      strcpy(array2[j]->smile, array[i]->smile);
      array2[j]->code          = array[i]->code;
      array2[j]->lingoSimValue = array[i]->lingoSimValue;

    }

    MixArray(array1, n1);
    MixArray(array2, n2);
    MixArrayX(array1, n1, array2, n2, array);

    for (int i = 0; i < n1; i++) if (array1[i]) delete array1[i];
    if (array1) delete[] array1;
    for (int i = 0; i < n2; i++) if (array2[i]) delete array2[i];
    if (array2) delete[] array2;

  }

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void MergeSort(LINGOSim **array, int n) {

  MixArray(array, n);

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
char* splitPosLine(char *line, int pos) {

  if (pos < 0) return NULL;

  int   count = 1, length = 0;
  char *tmpResult = new char[strlen(line) + 1];

  for (int i = 0; i < strlen(line); i++) {
    if (line[i] == ' ,') { count++; i++; }
    if (pos == count) tmpResult[length++] = line[i];
  }
  tmpResult[length] = '\0';

  char *result = new char[length + 1];
  strcpy(result, tmpResult);
  delete[] tmpResult;

  return result;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
LINGOSim* extractMol(char *line) {

  LINGOSim *result = new LINGOSim;
  result->smile          = splitPosLine(line, 1);
  char *received = splitPosLine(line, 2);
  result->code           = atoi(received);
  delete[] received;
  received = splitPosLine(line, 3);
  result->lingoSimValue  = atof(received);
  delete[] received;
   return result;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
LINGOSim **uncompressResult(char **result, int numberPart, int numberMolecules) {

  LINGOSim **listMatch = new LINGOSim*[numberMolecules];
  int       countMolecules = 0;

  for (int i = 0; i < numberPart; i++) {

    char *pch = strtok(result[i], "\n");
    if (pch) listMatch[countMolecules++] = extractMol(pch);

    while (pch != NULL) {
      pch = strtok(NULL, "\n");
      if (!pch) break;
      listMatch[countMolecules++] = extractMol(pch);
    }

  }

  return listMatch;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
bool prioriAnalysis(int length1, int length2, float umbral) {

  if ((1 - ((abs(length1 - length2))/(length1 + length2 - 6))) >= umbral) return true;
  return false;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
bool analysisCompResult(char *filename, int numberMol, int &countLine) {

  if (!numberMol || !filename) return false;

  FILE *File = fopen(filename, "r");
  countLine  = 0;

  if (!File) return false;

  char buffer[1024];

  while (!feof(File)) {
    fgets(buffer, 1024, File);
    if (strlen(buffer)) countLine++;
  }
  fclose(File);
  if (countLine - 1 == numberMol) return true;
  return false;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
bool deleteMolPosition(char **compressResultData, int position, int rank) {

  if (position < 1) return NULL;

  char *temporalResultData = new char[strlen(*compressResultData) + 1];
  strcpy(temporalResultData, *compressResultData);

  *(compressResultData)[0] = '\0';

  char *pch           = strtok(temporalResultData,"\n");
  int   internalCount = 1;
  char  name[300];

  sprintf(name, "errorMolecules%02d\0", rank);
  FILE *File = fopen(name, "a+");

  while (pch != NULL) {
    if (internalCount++ != position) sprintf(*compressResultData, "%s%s\n\0", *compressResultData, pch);
    else                             fprintf(File, "%s\n", pch);
    pch = strtok(NULL, "\n");
  }

  fclose(File);

  delete[] temporalResultData;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void standardizeMolecules(char **compressResultData, char *xmlConvertion, int rank, int numberIter, int numberMol) {

  printf("Node %d create Temporal File (Iter %d)\n", rank, numberIter);

  bool  condition     = true;
  char *nameCompStand = new char[400];

  while (condition) {

    int countLine = 0;

    sprintf(nameCompStand, "standardize%02d.smile\0", rank);
    FILE *computeStandardizeFile = fopen(nameCompStand, "w");
    fwrite(*compressResultData, 1, strlen(*compressResultData), computeStandardizeFile);
    fclose(computeStandardizeFile);

    printf("Node %d execute (Iter %d) -> ./standardize -c %s standardize%02d.smile > out%02d.smileOut\n", rank, numberIter, xmlConvertion, rank, rank);

    sprintf(nameCompStand, "./standardize -c %s standardize%02d.smile > out%02d.smileOut\0", xmlConvertion, rank, rank);
    try { system(nameCompStand); }
    catch (std::exception&) {}

    printf("Node %d open Result File (Iter %d)\n", rank, numberIter);
    sprintf(nameCompStand, "out%02d.smileOut\0", rank);

    if (analysisCompResult(nameCompStand, numberMol, countLine)) break;
    else {

      printf("Node %d Delete Molecule %d (Iter %d)\n", rank, countLine, numberIter);

      deleteMolPosition(compressResultData, countLine, rank);

      sprintf(nameCompStand, "rm standardize%02d.smile out%02d.smileOut\0", rank, rank);
      system(nameCompStand);

      numberMol--;

    }

  }

  long   lSize;

  sprintf(nameCompStand, "out%02d.smileOut\0", rank);
  FILE *computeStandardizeFile = fopen(nameCompStand, "r");

  fseek(computeStandardizeFile , 0 , SEEK_END);
  lSize = ftell(computeStandardizeFile);
  rewind(computeStandardizeFile);

  delete[] *compressResultData;
  *compressResultData = new char[lSize + 1];
  fread(*compressResultData, 1, lSize, computeStandardizeFile);
  (*compressResultData)[lSize] = '\0';

  fclose(computeStandardizeFile);

  printf("Node %d execute (Iter %d) -> rm standardize%02d.smile out%02d.smileOut\n", rank, numberIter, rank, rank);

  sprintf(nameCompStand, "rm standardize%02d.smile out%02d.smileOut\0", rank, rank);
  system(nameCompStand);

  delete[] nameCompStand;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
bool checkMolErr(char *molecule, char *erroMolecules) {

  char *temporalMolecule = new char[strlen(erroMolecules) + 1];
  strcpy(temporalMolecule, erroMolecules);
  char *pch              = strtok(temporalMolecule,"\n");

  while (pch != NULL) {
    trim(pch); trim(molecule);
    if (!strcmp(molecule, pch)) {
      delete[] temporalMolecule;
      return true;
    }
    pch = strtok(NULL, "\n");
  }
  delete[] temporalMolecule;
  return false;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void storedDiffCharacter(char *string, int **pos){

  if (!string || !pos) return;
  for (int i = 0; i < strlen(string); i++) if (!iscntrl(string[i])) (*pos)[((int)string[i])] = 1;
    
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int main(int argc, char *argv[]) {

  time_t        start,end;

  char         *dbName = NULL, *compressResultData = NULL;

  int           err, rank, sizeRank;            //Variable to use in MPI functions

  MPI_Request	reqs[ARRAYSIZE];	        //Variable to use in MPI functions
  MPI_Status	Stat;                           //Variable to use in MPI functions

  time (&start);

  //Initializate the Parallel Conection
  err = MPI_Init(&argc, &argv);

  if (err == MPI_SUCCESS) {
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &sizeRank);
  }
  else {
    rank     = 0;
    sizeRank = 1;
  }

  //Gte the input parameters 1. Database 2. The SMILES string
  if (argc < 2) {
    printf("Node %d Aborted.\n", rank);
    MPI_Finalize();
    return NULL;
  }
  else {
    dbName          = new char[strlen(argv[1]) + 1];
    strcpy(dbName, argv[1]);
  }


  //Compute and split the database for each Node
  long *flags;

  printf("Virtual Split of Database %s in Node %d...\n", dbName, rank);

  if (!rank) {

    printf("Obtaining the flags in root Node (%s)...\n", dbName);

    flags = findVirtualFlags(dbName, sizeRank, flags);

    int abort = 0;

    if (!flags) {

      abort = 1;

      for (int i = 1; i < sizeRank; i++)
        MPI_Send(&abort, 1, MPI_INT, i, (10 + i), MPI_COMM_WORLD);

      MPI_Finalize(); return 1;

    }
    for (int i = 1; i < sizeRank; i++) {
      MPI_Send(&abort, 1, MPI_INT, i, (10 + i), MPI_COMM_WORLD);
      MPI_Send(flags, sizeRank, MPI_LONG, i, (20 + i), MPI_COMM_WORLD);
    }

  }
  else {

    int abort;

    MPI_Recv(&abort, 1, MPI_INT, 0, (10 + rank), MPI_COMM_WORLD, &Stat);

    if (abort) { MPI_Finalize(); return 1; }

    flags = new long[sizeRank];

    MPI_Recv(flags, sizeRank, MPI_LONG, 0, (20 + rank), MPI_COMM_WORLD, &Stat);

  }

  if (err == MPI_SUCCESS) MPI_Barrier(MPI_COMM_WORLD);

  printf("Preparing the Files in Node %d...\n", rank);

  //Compute the LingoSim for all molecules
  char buffer[LENGTHARRAY], bufferTmp[LENGTHARRAY];
  int  numberMol = 0;
  long initial   = ((!rank)?0:flags[rank - 1]), finalR = flags[rank];

  gzFile completeDB            = gzopen(dbName,        "rb9");
  gzseek(completeDB, initial, SEEK_SET);

  int *characters = new int[255];

  while (!gzeof(completeDB)) {

    gzgets(completeDB, buffer, sizeof(buffer));
    if (!buffer) continue;
    strcpy(bufferTmp, buffer);

    

    long  endPosTmp = gztell(completeDB);

    if (endPosTmp >= finalR) break;

  }

  printf("Preparing to Join the Result Process in Node %d...\n", rank);

  //Group the result in the master Node
  long  sizeCompressResultData = 0;

  if (rank) {

    printf("Sending to root Node the result obtained in Node %d...\n", rank);

    MPI_Send(compressResultData     , sizeCompressResultData, MPI_CHAR, 0, (40 + rank), MPI_COMM_WORLD);


  }
  else {

    for (int i = 1; i < sizeRank; i++) {

      MPI_Recv(compressResultData     , sizeCompressResultData, MPI_CHAR, i, (40 + i), MPI_COMM_WORLD, &Stat);

    }

    printf("Finish...\n");

  }

  if (err == MPI_SUCCESS) MPI_Barrier(MPI_COMM_WORLD);

  if (dbName)           delete[] dbName;
  if (flags)            delete[] flags;

  time (&end);
  if (!rank) {
      int timeSeconds = difftime(end,start);
      int timeMinutes = (timeSeconds/60);
      int timeHours   = (timeMinutes/60);
      timeSeconds    %= 60;
      timeMinutes    %= 60;
      printf("Elapsed Time: %02d:%02d:%02d (HH:MM:SS)\n", timeHours, timeMinutes, timeSeconds);
  }

  if (err == MPI_SUCCESS) MPI_Finalize();

  return 1;

}
